﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.Statistics.RandomNumberGenerator
{
    /// <summary>
    /// This class provides the generation of random numbers by using the Muller-Box method.
    /// </summary>
    [Serializable]
    public class GaussianRandomGenerator : AbstractRandomGenerator
    {
        /// <summary>
        /// This field holds the mean.
        /// </summary>
        private readonly double m;

        /// <summary>
        /// This field holds the standard deviation.
        /// </summary>
        private readonly double s;

        /// <summary>
        /// Internal field, which holds the second number.
        /// </summary>
        private double store;

        /// <summary>
        /// Internal field, which describes if the internal store includes a random number.
        /// </summary>
        private bool numberInStore;

        /// <summary>
        /// Internal field, which holds the random engine.
        /// </summary>
        private readonly System.Random randomEngine;

        /// <summary>
        /// Initializes a new instance of the <see cref="GaussianRandomGenerator"/> class.
        /// </summary>
        public GaussianRandomGenerator()
            : base((uint) DateTime.Now.Millisecond)
        {
            this.m = 0;
            this.s = 1;
            this.numberInStore = false;
            this.randomEngine = new System.Random((int) this.Seed);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GaussianRandomGenerator"/> class.
        /// </summary>
        /// <param name="seed">A number used to calculate a starting value for the pseudo-random number sequence.
        /// If a negative number is specified, the absolute value of the number is used.</param>
        public GaussianRandomGenerator(uint seed)
            : base(seed)
        {
            this.m = 0;
            this.s = 1;
            this.numberInStore = false;
            this.randomEngine = new System.Random((int) this.Seed);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GaussianRandomGenerator"/> class.
        /// </summary>
        /// <param name="m">The mean m.</param>
        /// <param name="s">The standard deviation s.</param>
        public GaussianRandomGenerator(double m, double s)
            : base((uint) DateTime.Now.Millisecond)
        {
            this.m = m;
            this.s = s;
            this.numberInStore = false;
            this.randomEngine = new System.Random((int) this.Seed);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GaussianRandomGenerator"/> class.
        /// </summary>
        /// <param name="m">The mean m.</param>
        /// <param name="s">The standard deviation s.</param>
        /// <param name="seed">A number used to calculate a starting value for the pseudo-random number sequence. 
        /// If a negative number is specified, the absolute value of the number is used.</param>
        public GaussianRandomGenerator(double m, double s, uint seed)
            : base(seed)
        {
            this.m = m;
            this.s = s;
            this.numberInStore = false;
            this.randomEngine = new System.Random((int) this.Seed);
        }

        /// <summary>
        /// Returns a nonnegative random number.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next()
        {
            return this.Next(0, int.MaxValue);
        }

        /// <summary>
        /// Nexts the specified max value.
        /// </summary>
        /// <param name="maxValue">Returns a nonnegative random number less than the specified maximum.</param>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next(int maxValue)
        {
            return this.Next(0, maxValue);
        }

        /// <summary>
        /// Returns a random number within a specified range.
        /// </summary>
        /// <param name="minValue">The inclusive lower bound of the random number returned.</param>
        /// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be
        /// greater than or equal to minValue.</param>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next(int minValue, int maxValue)
        {
            if (minValue < 0)
            {
                minValue = 0;
            }

            if (maxValue == int.MaxValue)
            {
                maxValue -= 1;
            }

            if (minValue > maxValue)
            {
                int tempuri = maxValue;

                maxValue = minValue;
                minValue = tempuri;
            }

            double number = this.GenerateRandomNumber();

            if (number < 0)
            {
                number = Math.Abs(number);
            }

            if (number > 1)
            {
                number = number - Math.Truncate(number);
            }

            return (int) (Math.Floor((maxValue - minValue + 1)*number + minValue));
        }

        /// <summary>
        /// Returns a random number between 0.0 and 1.0.
        /// </summary>
        /// <returns>
        /// A double-precision floating point number greater than or equal to 0.0, and less
        /// than 1.0.
        /// </returns>
        public override double NextDouble()
        {
            return this.GenerateRandomNumber();
        }

        private double GenerateRandomNumber()
        {
            if (this.numberInStore)
            {
                this.numberInStore = false;

                return (this.m + this.store*this.s);
            }

            double w;
            double z1;
            double z2;

            do
            {
                z1 = 2.0*this.randomEngine.NextDouble() - 1.0;
                z2 = 2.0*this.randomEngine.NextDouble() - 1.0;
                w = Math.Pow(z1, 2) + Math.Pow(z2, 2);
            } while (w >= 1.0);

            w = Math.Sqrt((-2.0*Math.Log(w))/w);
            this.store = z2*w;
            this.numberInStore = true;

            return (this.s*(z1*w) + this.m);
        }
    }
}